/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

const { reject } = require("lodash")

// 状态：pending: 等待，fulfill: 成功，rejected: 失败
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function MyPromise(callback) {
  this.status = PENDING
  this.value = void 0
  this.reason = void 0
  this.onResolvedCallbacks = []
  this.onRejectedCallbacks = []

  this.resolve = function(value) {
    if (value instanceof MyPromise) return value.then(this.resolve, this.reject)

    if (this.status === PENDDING) {
      this.status = FULFILLED
      this.value = value
      this.onResolvedCallbacks.forEach(cb => cb())
    }
  }
  this.reject = function(reason) {
    if (this.status === PENDDING) {
      this.status = REJECTED
      this.reason = reason
      this.onRejectedCallbacks.forEach(cb => cb())
    }
  }

  try {
    callback(this.resolve, this.reject)
  } catch (error) {
    this.reject(error)
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  onResolved = typeof onResolved === 'function' ? onResolved : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

  let promise2 = new MyPromise((resolve, reject) => {
    if (this.status === FULFILLED) {
      setTimeout(() => {
        try {
          let x = onResolved(this.value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0)
    }

    if (this.status === REJECTED) {
      setTimeout(() => {
        try {
          let x = onRejected(this.value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0);
    }

    if (this.status === PENDDING) {
      this.onResolvedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onResolved(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0);
      })
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            let x = onRejected(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0);
      })
    }
  })

  return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise!"))
  }

  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

MyPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

MyPromise.prototype.finally = function(callback) {
  return this.then(value => {
    return MyPromise.resolve(callback()).then(() => value)
  }, reason => {
    return MyPromise.reject(callback()).then(() => { throw reason })
  })
}

MyPromise.all = function(arr) {

  return new MyPromise((resolve, reject) => {
    let result = []
    let index = 0
    
    for(let i = 0; i<arr.length; i++) {
      let cur = arr[i]
      if (cur instanceof MyPromise) {
        cur.then(value => result.push(value), reason => reject(reason))
      } else {
        result.push(cur)
      }

      index ++
      if (index === arr.length) {
        return resolve(result)
      }
    }
  })
}